/* Copyright 2010 Michael Bruno
 *
 * This file is part of AYACE
 *
 * AYACE is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * at your option) any later version.
 *
 * AYACE is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with AYACE.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include <pthread.h>
#include <ctype.h>
#include "common.h"
#include "bitboard/bitboard.h"
#include "bitboard/bitboard_init.h"
#include "bitboard/bitboard_movelist.h"
#include "xboard_io.h"
#include "queue.h"
#include "hash.h"
#include "chess.h"
#include "move_search.h"

char *pname;
FILE *of = NULL;
pthread_mutex_t stdout_lock;
int engine_color = BLACK, current_color = WHITE;
int force = 0, edit = 0, edit_color;

ttable_t ttable;
board_t *move_history = NULL;
int move_history_index, move_history_size;

int trait_search_depth = 5;		// 5
int trait_defensiveness = 3;		// 1
int trait_offensiveness = 1;		// 0
int trait_king_protectiveness = 2;	// 2
int trait_king_attackiveness = 1;	// 2
int trait_queen_protectiveness = 3;	// 3
int trait_queen_attackiveness = 2;	// 2


void move_t2mstr(char *mstr, move_t m)
{
	int f, t;

	f = m.from;
	t = m.to;

	if (m.new_piece) {
		char pc[8] = {' ', 'p', 'n', 'k', ' ', 'b', 'r' ,'q'};
		snprintf(mstr, 6, "%c%c%c%c%c", f%8+'a', f/8+'1', t%8+'a', t/8+'1', pc[m.new_piece]);
	} else {
		snprintf(mstr, 5, "%c%c%c%c", f%8+'a', f/8+'1', t%8+'a', t/8+'1');
	}
}

move_t mstr2move_t(board_t *b, char *mstr)
{
	int color;
	move_t m;

	m.from = COORD(mstr);
	m.to = COORD(mstr+2);

	color = get_piece_color(b, m.from);
	m.moving_piece = get_piece_type(b, m.from, color);
	if (m.moving_piece == PAWN && (piece_setmask[m.to] & b->color[!color].pawn_shadow)) {
		m.captured_piece = PAWN;
	} else {
		m.captured_piece = get_piece_type(b, m.to, !color);
	}

	if (m.moving_piece == PAWN && (mstr[3] == '1' || mstr[3] == '8')) {
		switch (mstr[4]) {
		case 'n':
			m.new_piece = KNIGHT;
			break;
		case 'b':
			m.new_piece = BISHOP;
			break;
		case 'r':
			m.new_piece = ROOK;
			break;
		case 'q':
		default:
			m.new_piece = QUEEN;
			break;
		}
	} else {
		m.new_piece = 0;
	}

	return m;
}

int in_list(move_t *move_list, int move_count, move_t m, move_t *lm)
{
	int i;

	for (i=0; i<move_count; i++) {
		if (move_list->to == m.to && move_list->from == m.from) {
			if (lm)
				*lm = *move_list;
			return 1;
		}
		move_list++;
	}

	return 0;
}

int no_legal_moves(board_t *b, move_t *move_list, int move_count, int color)
{
	int i;
	board_t b2;

	for (i=0; i<move_count; i++) {
		b2 = *b;
		if (!move_piece(b, *move_list++, color)) {
			*b = b2;
			return 0;
		}
	}

	return 1;
}

void print_moves(move_t *move_list, int move_count)
{
	int i;

	for (i=0; i<move_count; i++) {
		int from = move_list[i].from;
		int to = move_list[i].to;
		fprintf(of, "%2d: %c%c to %c%c\n", i, from%8 + 'a', from/8 + '1', to%8 + 'a', to/8 + '1');
	}
	fprintf(of, "\n");
}

// return -1 for invalid usermove
// return 1 for checkmate, 2 for stalemate
// return 0 otherwise
int make_move(board_t *b, char *usermove)
{
	int gameover = 0;
	int move_count;
	char command[32];
	move_t m;
	move_t move_list[256];

	if (usermove == NULL) {
		fprintf(of, "MY TURN\n");
		fflush(of);
	}

	move_count = get_move_list(b, move_list, current_color);
//	fprintf(of,"Legal moves:\n");
//	print_moves(move_list);
//	fflush(of);

	if (move_count > 0) {

		if (usermove != NULL) {
			// user is moving
			m = mstr2move_t(b, usermove);

			if (!in_list(move_list, move_count, m, NULL)) {
				// move was not even in list, so it's illegal
				snprintf(command, 32, "Illegal move: %s", usermove);
				send_command(command);
				return -1;
			} else {
				// make the move
				if (move_piece(b, m, current_color)) {
					// move put king in check
					snprintf(command, 32, "Illegal move: %s", usermove);
					send_command(command);
					return -1;
				}
			}
		} else {
			int t, f;

			// engine must move
			m = find_best_move(b, move_list, move_count, trait_search_depth);
			t = m.to;
			f = m.from;
			if (m.new_piece) {
				char pc[8] = {' ', 'p', 'n', 'k', ' ', 'b', 'r' ,'q'};
				snprintf(command, 32, "move %c%c%c%c%c", f%8+'a', f/8+'1', t%8+'a', t/8+'1', pc[m.new_piece]);
			} else {
				snprintf(command, 32, "move %c%c%c%c", f%8+'a', f/8+'1', t%8+'a', t/8+'1');
			}

			send_command(command);
			move_piece(b, m, current_color);
		}

		
		if (move_history_index == move_history_size-1) {
			move_history_size += 50;
			move_history = realloc(move_history, move_history_size*sizeof(board_t));
		}
		move_history[++move_history_index] = *b;


		print_board(of, b);
	//	display_bitboard(of, b->color[WHITE].pawn_shadow | b->color[BLACK].pawn_shadow);
		fprintf(of, "Current score: %d\n", board_score(b));
		
		current_color = !current_color;

		move_count = get_move_list(b, move_list, current_color);
		if (no_legal_moves(b, move_list, move_count, current_color)) {
			// was that checkmate or stalemate?
			
			if (get_attacks_to(b, last_one_64(b->color[current_color].king), !current_color)) {
				// checkmate
				snprintf(command, 32, "%s mates}", current_color == WHITE ? "0-1 {black" : "1-0 {white");
				gameover = 1;
			} else {
				// stalemate
				snprintf(command, 32, "1/2-1/2 {Stalemate}");
				gameover = 2;
			}
			send_command(command);

		}
	}

//	fprintf(of, "Move history:\n");
//	print_moves(get_head(move_history));
//	print_board(of, &cur_board);
//	fflush(of);

	return gameover;
}

int main(int argc, char **argv)
{
	board_t cur_board;
	char command[32];
	pthread_t id;
	xboard_cmd_t xbd_cmd;

	pname = *argv;

	signal(SIGINT, quit);

	of = fopen("chess_debug", "a");
	xboard_commands = create_queue(0);
	
	
	if (pthread_create(&id, NULL, xboard_listen, NULL))
		emit_error("Error creating xboard_listen thread");

	pthread_mutex_init(&stdout_lock, NULL);

	pthread_detach(id);

	bitboard_system_init();
	init_hash_numbers(0x4891356702DACFBEULL);
	ttable = create_ttable(23);
	srandom(time(NULL));
	

	while(1) {
	//	move_count = get_move_list(&cur_board, move_list, current_color);
	//	print_moves(move_list, move_count);

		// wait here for the user to move
		fprintf(of, "WAITING FOR XBOARD\n");
		fflush(of);
		dequeue(xboard_commands, &xbd_cmd);
		fprintf(of, "RECEIVED XBOARD COMMAND\n");
		fflush(of);

		switch(xbd_cmd.cmd) {
		case XBC_PROTOVER:
			fprintf(of, "CMD: PROTOVER\n");

			send_command("feature myname=\"AYACE 0.42a\" ping=1 sigint=0 sigterm=0 usermove=1 colors=0 done=1");

			break;
		case XBC_PING:
			fprintf(of, "CMD: PING\n");

			snprintf(command, 32, "pong %s", xbd_cmd.arg);
			send_command(command);

			break;
		case XBC_NEW:
			fprintf(of, "CMD: NEW\n");
			cur_board = reset_board();

			free(move_history);
			move_history_size = 50;
			move_history = calloc(move_history_size, sizeof(board_t));
			move_history_index = 0;
			move_history[move_history_index] = cur_board;
			
			engine_color = BLACK;
			current_color = WHITE;
			force = 0;
			print_board(of, &cur_board);
			fflush(of);

			break;
		case XBC_USERMOVE:
			fprintf(of, "CMD: USERMOVE\n");
			
			if (make_move(&cur_board, xbd_cmd.arg))
				break;

			if (!force) {
				make_move(&cur_board, NULL);
			}
					
			break;
		case XBC_FORCE:
			fprintf(of, "CMD: FORCE\n");
			
			force = 1;
			
			break;
		case XBC_GO:
			fprintf(of, "CMD: GO\n");
			
			force = 0;
			engine_color = current_color;
			make_move(&cur_board, NULL);

			break;
		case XBC_EDIT:

			if (xbd_cmd.arg[0] == '\0') {
				edit_color = 0;
			} else if (xbd_cmd.arg[0] == '.') {
				cur_board.qscastle[WHITE] = 0;
				cur_board.qscastle[BLACK] = 0;
				cur_board.kscastle[WHITE] = 0;
				cur_board.kscastle[BLACK] = 0;

				if (cur_board.color[WHITE].king & piece_setmask[4]) {
					// white king is in its home square
					if (cur_board.color[WHITE].rooks & piece_setmask[0]) {
						// left white rook is in its home square
						cur_board.qscastle[WHITE] = 1;
					}
					if (cur_board.color[WHITE].rooks & piece_setmask[7]) {
						// right white rook is in its home square
						cur_board.kscastle[WHITE] = 1;
					}
				}

				if (cur_board.color[BLACK].king & piece_setmask[4+56]) {
					// black king is in its home square
					if (cur_board.color[BLACK].rooks & piece_setmask[0+56]) {
						// left black rook is in its home square
						cur_board.qscastle[BLACK] = 1;
					}
					if (cur_board.color[BLACK].rooks & piece_setmask[7+56]) {
						// right black rook is in its home square
						cur_board.kscastle[BLACK] = 1;
					}
				}

				
				free(move_history);
				move_history_size = 50;
				move_history = calloc(move_history_size, sizeof(board_t));
				move_history_index = 0;
				move_history[move_history_index] = cur_board;

				print_board(of, &cur_board);
				fflush(of);
			} else if (xbd_cmd.arg[0] == '#') {
				cur_board = clear_board();
			} else if (tolower((int)xbd_cmd.arg[0]) == 'c') {
				edit_color = !edit_color;
			} else {
				int piece, sq;
				bitboard *pb;

				xbd_cmd.arg[0] = tolower((int)xbd_cmd.arg[0]);
				xbd_cmd.arg[1] = tolower((int)xbd_cmd.arg[1]);
				xbd_cmd.arg[2] = tolower((int)xbd_cmd.arg[2]);

				sq = COORD(xbd_cmd.arg+1);

				switch (xbd_cmd.arg[0]) {
				case 'p':
					piece = PAWN;
					break;
				case 'n':
					piece = KNIGHT;
					break;
				case 'k':
					piece = KING;
					break;
				case 'b':
					piece = BISHOP;
					break;
				case 'r':
					piece = ROOK;
					break;
				case 'q':
					piece = QUEEN;
					break;
				case 'x':
				default:
					piece = 0;
					break;
				}
				
				if (piece) {
					pb = get_piece_board_p(&cur_board, edit_color, piece);
					*pb |= piece_setmask[sq];
					cur_board.color[edit_color].all |= piece_setmask[sq];
					cur_board.all_pieces |= piece_setmask[sq];
					cur_board.all_pieces_rl90 |= piece_setmask_rl90[sq];
					cur_board.all_pieces_rl45 |= piece_setmask_rl45[sq];
					cur_board.all_pieces_rr45 |= piece_setmask_rr45[sq];
				} else {
					// xboard doesn't actually use this anyway..
				}
			}

			break;
		case XBC_UNDO:
			fprintf(of, "CMD: UNDO\n");

			if (move_history_index == 0)
				break;

			cur_board = move_history[--move_history_index];
			current_color = !current_color;
			print_board(of, &cur_board);
			fflush(of);

			break;
		case XBC_BK:
			fprintf(of, "CMD: BK\n");
			{
				uint64_t key;
				int ccolor = current_color;
				ttable_entry *te;

				current_color = engine_color;
				snprintf(command, 32, "\tCurrent board score: %d", board_score(&cur_board));
				send_command(command);
				
				key = gen_zobrist_key(&cur_board, current_color);
				snprintf(command, 32, "\tHASH: %llX", key);
				send_command(command);

				te = ttable_lookup(ttable, key);
				if (te->key != key) {
					te->key = key;
					snprintf(command, 32, "\tNOT FOUND IN TRANSPOSITION TABLE!");
				} else {
					snprintf(command, 32, "\tFOUND IN TRANSPOSITION TABLE!");
				}
				send_command(command);

				current_color = ccolor;
			}
			snprintf(command, 32, "\tIt is %s's turn", current_color == WHITE ? "white" : "black");
			send_command(command);
			snprintf(command, 32, "\tEngine is playing %s", engine_color == WHITE ? "white" : "black");
			send_command(command);
			

			send_command("\n");
		
			break;
		case XBC_QUIT:
			fprintf(of, "CMD: QUIT\n");



			break;
		}
	}
}

